#include "nemu.h"
#include "memory/cache.h"
#include <time.h>
#include "burst.h"
#include <stdlib.h>

// 初始化缓存，确保所有缓存行开始时都是假值
void init_cache() //初始化中做到的事：
{
  int i;

  // 初始化L1缓存的所有行，设置为无效状态
  for (i = 0; i < CACHE_L1_SET_NUM * CACHE_L1_WAY_NUM; i++) {
    cache_L1[i].validVal = false;
  }

  // 初始化L2缓存的所有行，设置为无效且非脏状态
  for (i = 0; i < CACHE_L2_SET_NUM * CACHE_L2_WAY_NUM; i++) {
    cache_L2[i].dirtyVal = false;
    cache_L2[i].validVal = false;
  }

  // 函数执行完毕，缓存初始化完成
  return;
}

void ddr3_read_me(hwaddr_t addr, void* data);//读存机制

// return whole index of way in cacheL1
/**
 * 读取L1缓存中的数据
 * 
 * 当L1缓存命中时，返回缓存行的索引；当未命中时，更新L1缓存并返回新缓存行的索引
 * 
 * @param addr 物理地址，用于访问缓存
 * @return 返回缓存行的索引，如果发生错误，返回-1
 */
int read_cache_L1(hwaddr_t addr) {
  // 计算缓存组的索引
  uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
  // 提取标签
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));
  // uint32_t block_start = ((addr >> CACHE_BLOCK_BIT) << CACHE_BLOCK_BIT);

  int wayIndex;
  // 计算当前组开始的缓存行索引
  int whole_begin_wayIndex = setIndex * CACHE_L1_WAY_NUM;
  // 计算下一组开始的缓存行索引
  int whole_end_wayIndex = (setIndex + 1) * CACHE_L1_WAY_NUM;

  // 遍历当前组内的所有缓存行，寻找命中
  for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
    // 如果当前缓存行有效且标签匹配，则命中，返回缓存行索引
    if (cache_L1[wayIndex].validVal && cache_L1[wayIndex].tag == tag) // Hit!
      return wayIndex;

  // 缓存不命中下的状态
  srand(time(0));
  // 从L2缓存中读取数据
  int wayIndex_L2 = read_cache_L2(addr);
  // 随机选择一个缓存行进行替换
  wayIndex = whole_begin_wayIndex + rand() % CACHE_L1_WAY_NUM;
  // 复制数据到L1缓存中
  memcpy(cache_L1[wayIndex].data, cache_L2[wayIndex_L2].data, CACHE_BLOCK_SIZE);

  // 更新缓存行的有效位和标签
  cache_L1[wayIndex].validVal = true;
  cache_L1[wayIndex].tag = tag;
  // 返回新缓存行的索引
  return wayIndex;
}

void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask);

// return whole index of way in cacheL2
/**
 * 从L2缓存中读取数据。
 * 
 * 此函数尝试从L2缓存中读取指定地址的数据。如果数据在缓存中存在（命中），则直接返回缓存项的索引。
 * 如果数据不存在（未命中），则根据缓存替换策略选择一个缓存项进行替换，并将新数据加载到缓存中。
 * 
 * @param addr 要读取的地址。
 * @return 返回缓存项的索引，表示数据在缓存中的位置。
 */
int read_cache_L2(hwaddr_t addr) {
  uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
  uint32_t block_start = ((addr >> CACHE_BLOCK_BIT) << CACHE_BLOCK_BIT);

  int wayIndex;
  int whole_begin_wayIndex = setIndex * CACHE_L2_WAY_NUM;
  int whole_end_wayIndex = (setIndex + 1) * CACHE_L2_WAY_NUM;
  for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++)
    if (cache_L2[wayIndex].validVal && cache_L2[wayIndex].tag == tag) return wayIndex; // Hit!
  // Hit loss!
  srand(time(0));
  wayIndex = whole_begin_wayIndex + rand() % CACHE_L2_WAY_NUM;
  int i;
  if (cache_L2[wayIndex].validVal && cache_L2[wayIndex].dirtyVal) {
    // write down
    uint8_t tmp[BURST_LEN << 1];
    memset(tmp, 1, sizeof(tmp));
    uint32_t block_start_x = (cache_L2[wayIndex].tag << (CACHE_L2_SET_BIT + CACHE_BLOCK_BIT)) | (setIndex << CACHE_BLOCK_BIT);
    for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++) {
      ddr3_write_me(block_start_x + BURST_LEN * i, cache_L2[wayIndex].data + BURST_LEN * i, tmp);
    }
  }
  for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++) {
    ddr3_read_me(block_start + BURST_LEN * i, cache_L2[wayIndex].data + BURST_LEN * i);
  }
  cache_L2[wayIndex].validVal = true;
  cache_L2[wayIndex].dirtyVal = false;
  cache_L2[wayIndex].tag = tag;
  return wayIndex;
}

void dram_write(hwaddr_t addr, size_t len, uint32_t data);

/**
 * 将数据写入L1缓存。
 * 如果数据已经在缓存中（命中），则更新L1缓存中的数据，并将其写入下一级（L2缓存）。
 *
 * @param addr 缓存地址
 * @param len 数据长度
 * @param data 要写入的数据
 */
void write_cache_L1(hwaddr_t addr, size_t len, uint32_t data) {
  uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));
  uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  int wayIndex;
  int whole_begin_wayIndex = setIndex * CACHE_L1_WAY_NUM;
  int whole_end_wayIndex = (setIndex + 1) * CACHE_L1_WAY_NUM;

  // 遍历当前set的所有way，查找匹配的tag
  for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++) {
    if (cache_L1[wayIndex].validVal && cache_L1[wayIndex].tag == tag) {
      // 命中
      // 写穿透
      if (block_bias + len > CACHE_BLOCK_SIZE) {
        // 数据跨越多个缓存块，需要分段处理
        dram_write(addr, CACHE_BLOCK_SIZE - block_bias, data);
        memcpy(cache_L1[wayIndex].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        write_cache_L2(addr, CACHE_BLOCK_SIZE - block_bias, data);
        write_cache_L1(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
      } else {
        // 数据在单个缓存块内
        dram_write(addr, len, data);
        memcpy(cache_L1[wayIndex].data + block_bias, &data, len);
        write_cache_L2(addr, len, data);
      }
      return;
    }
  }

  // 未命中
  // 不进行写分配
  write_cache_L2(addr, len, data);
  return;
}

/**
 * 将数据写入L2缓存指定地址。
 * 
 * 此函数将指定长度的数据写入L2缓存的指定地址。如果数据跨越多个缓存块，它会递归地写入后续的缓存块。如果缓存行不在缓存中，
 * 它会先将其读入缓存再进行写操作。
 * 
 * @param addr 要写入缓存的地址。
 * @param len 要写入的数据长度。
 * @param data 要写入缓存的数据。
 */
void write_cache_L2(hwaddr_t addr, size_t len, uint32_t data) {
  // 根据地址计算缓存集索引。
  uint32_t setIndex = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
  // 从地址中提取标签。
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
  // 计算缓存块内的字节偏移量。
  uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  int wayIndex;
  // 确定该缓存集中所有方式的起始和结束索引。
  int whole_begin_wayIndex = setIndex * CACHE_L2_WAY_NUM;
  int whole_end_wayIndex = (setIndex + 1) * CACHE_L2_WAY_NUM;
  
  // 遍历缓存集中的所有方式，查找缓存命中。
  for (wayIndex = whole_begin_wayIndex; wayIndex < whole_end_wayIndex; wayIndex++) {
    // 如果找到一个有效且标签匹配的缓存行，则为命中。
    if (cache_L2[wayIndex].validVal && cache_L2[wayIndex].tag == tag) {
      // 命中！
      // 标记缓存行为脏。
      cache_L2[wayIndex].dirtyVal = true;
      // 如果要写入的数据超出当前缓存块边界，分两部分写入。
      if (block_bias + len > CACHE_BLOCK_SIZE) {
        memcpy(cache_L2[wayIndex].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        // 递归写入剩余部分的数据。
        write_cache_L2(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
      } else {
        // 如果要写入的数据未超出当前缓存块边界，直接写入。
        memcpy(cache_L2[wayIndex].data + block_bias, &data, len);
      }
      return;
    }
  }
  
  // 如果没有缓存命中，执行写分配操作。
  // 首先，找到一个方式将缓存行读入缓存。
  wayIndex = read_cache_L2(addr);
  // 标记缓存行为脏。
  cache_L2[wayIndex].dirtyVal = true;
  // 直接将数据写入缓存。
  memcpy(cache_L2[wayIndex].data + block_bias, &data, len);
  return;
}
